﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using D2D2.Server.Core;
using D2D2.SharedInterfaces.Networking;
using D2D2.SharedInterfaces.SharedObject;
using Lidgren.Network;

namespace D2D2.Server.Networking
{
    public class ServerManager
    {
        private static NetServer _server;
        private SignalManager _signalManager;
        private double _lastUpdateTime;
        private double _lastSendTime;
        private List<IGameMessage> _messageQueue = new List<IGameMessage>();

        public ServerManager()
        {
            _signalManager = new SignalManager(this);
            PlayerManager = new PlayerManagerServer(this);
        }

        public BattlefieldServer Battlefield { get; set; }
        public PlayerManagerServer PlayerManager { get; private set; }

        public void Start()
        {
            var config = new NetPeerConfiguration("D2D2")
                             {
                                 Port = Convert.ToInt32("14242"),
                                 SimulatedMinimumLatency = 0.1f,
                                 // SimulatedLoss = 0.1f 
                             };

            config.EnableMessageType(NetIncomingMessageType.WarningMessage);
            config.EnableMessageType(NetIncomingMessageType.ErrorMessage);
            config.EnableMessageType(NetIncomingMessageType.Error);
            config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
            /** 
            config.EnableMessageType(NetIncomingMessageType.VerboseDebugMessage);
            config.EnableMessageType(NetIncomingMessageType.DebugMessage);
            */

            //config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);

            _server = new NetServer(config);
            _server.Start();

            _lastUpdateTime = NetTime.Now;

            while (!Console.KeyAvailable || Console.ReadKey().Key != ConsoleKey.Escape)
            {
                float updateElapsed = (float) (NetTime.Now - _lastUpdateTime);

                float serverTickRate = (float) GlobalConst.SERVER_TICK_RATE/1000;

                if (updateElapsed > serverTickRate)
                {
                    _lastUpdateTime = NetTime.Now;

                    NetIncomingMessage message;
                    while ((message = _server.ReadMessage()) != null)
                    {
                        switch (message.MessageType)
                        {
                            case NetIncomingMessageType.VerboseDebugMessage:
                            case NetIncomingMessageType.DebugMessage:
                            case NetIncomingMessageType.WarningMessage:
                            case NetIncomingMessageType.ErrorMessage:
                                Console.WriteLine(message.ReadString());
                                break;
                            case NetIncomingMessageType.StatusChanged:
                                HandleStatusChanged(message);
                                break;
                            case NetIncomingMessageType.Data:
                                HandleDataMessage(message);

                                break;
                        }

                    }

                    Update(updateElapsed);
                }

                float sendElapsed = (float) (NetTime.Now - _lastSendTime);

                if (sendElapsed > (float) GlobalConst.CLIENT_UPDATE_RATE/1000)
                {
                    _lastSendTime = NetTime.Now;

                    SendMessages();
                }
            }
        }

        private void Update(float elapsed)
        {

            if (Battlefield != null) Battlefield.Update(elapsed);
            if (PlayerManager != null) PlayerManager.Update(elapsed);
        }

        private void HandleStatusChanged(NetIncomingMessage message)
        {
            NetConnectionStatus status = (NetConnectionStatus) message.ReadByte();
            switch (status)
            {
                case NetConnectionStatus.Connected:
                    Console.WriteLine("{0} connected!",
                                      NetUtility.ToHexString(
                                          message.SenderConnection.RemoteUniqueIdentifier));
                    break;
                case NetConnectionStatus.RespondedAwaitingApproval:
                    NetOutgoingMessage hailMessage = _server.CreateMessage();
                    hailMessage.WriteTime(false);

                    PlayerObject playerObject = PlayerManager.AddPlayer();
                    playerObject.Encode(hailMessage);

                    message.SenderConnection.Approve(hailMessage);
                    Console.WriteLine(string.Format("Player {0} added.", playerObject.Id));
                    break;
            }
        }

        private void HandleDataMessage(NetIncomingMessage message)
        {
            GameMessageType messageType = (GameMessageType) message.ReadByte();

            switch (messageType)
            {
                case GameMessageType.Signal:
                    _signalManager.HandleSignal(message);
                    break;
                case GameMessageType.MouseMessage:
                    MouseMessage mouseMessage = new MouseMessage(message);

                    //Console.WriteLine(string.Format("Mouse Click: Player {0}, X={1}, Y={2}", mouseMessage.PlayerId,
                    //                                mouseMessage.X, mouseMessage.Y));

                    if (Battlefield != null) Battlefield.HandleMouseClick(mouseMessage);
                    break;
                case GameMessageType.TestMessage:
                    TestMessage testMessage = new TestMessage(message);
                    Console.WriteLine(string.Format("Receive: {0}. Now: {1}", testMessage.MessageTime, NetTime.Now));
                    TestMessage responseMessage = new TestMessage();
                    SendMessage(responseMessage);
                    break;
            }
        }

        public void SendMessage(IGameMessage gameMessage)
        {
            IGameMessage existMessage =
                _messageQueue.Find(m => m.Id == gameMessage.Id && m.MessageType == gameMessage.MessageType);

            if (existMessage != null) _messageQueue.Remove(existMessage);

            gameMessage.MessageTime = NetTime.Now;
            _messageQueue.Add(gameMessage);

        }

        private void SendMessages()
        {
            foreach (IGameMessage gameMessage in _messageQueue)
            {
                NetOutgoingMessage message = _server.CreateMessage();

                message.Write((byte) gameMessage.MessageType);
                message.WriteTime(gameMessage.MessageTime, false);
                gameMessage.Encode(message);

                _server.SendToAll(message, NetDeliveryMethod.ReliableOrdered);
            }

            _messageQueue.Clear();
        }
    }
}
